package com.eairlv.utils.config;

import com.alibaba.fastjson.JSON;
import com.eairlv.utils.MSInterceptorProperties;
import com.eairlv.utils.support.hystrix.CleanDataTransmittableThreadLocal;
import com.eairlv.utils.utils.LogUtil;
import com.eairlv.utils.validator.gateway.GatewayValidator;
import com.eairlv.utils.validator.url.HttpResult;
import com.eairlv.utils.validator.url.Result;
import com.eairlv.utils.validator.url.UrlValidator;
import com.eairlv.utils.validator.user.UserInformation;
import com.eairlv.utils.validator.user.UserValidator;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
 * @author lv
 * @create 2018-10-31 17:53
 * @desc 拦截器特殊说明（无法进入拦截器，但可通过过滤器进行拦截）：
 * <actuator>监控的所有接口使用endpoint注入，不属于{@link org.springframework.web.servlet.HandlerMapping}，因此请求无法进入HandlerInterceptor拦截器
 * /v2/api-docs属于{@link springfox.documentation.swagger2.web.Swagger2Controller}，而Swagger2Controller是通过Swagger2DocumentationConfiguration进行Bean的注入，而不通过@ComponentScan扫描注入
 * - Swagger2Controller {@link springfox.documentation.swagger2.web.Swagger2Controller}
 * - Swagger2DocumentationConfiguration {@link springfox.documentation.swagger2.configuration.Swagger2DocumentationConfiguration}
 **/
@Slf4j
public class MSInterceptor implements HandlerInterceptor {

    public static final ThreadLocal<UserInformation> USER_INFORMATION = new CleanDataTransmittableThreadLocal<>();
    public static final ThreadLocal<HttpServletRequest> REQUEST_INFORMATION = new CleanDataTransmittableThreadLocal<>();

    private MSInterceptorProperties msInterceptorProperties;

    private RestTemplate feignRestTemplate;

    private static String[] packages;

    @Autowired
    public MSInterceptor(MSInterceptorProperties msInterceptorProperties, RestTemplate feignRestTemplate) {
        this.msInterceptorProperties = msInterceptorProperties;
        this.feignRestTemplate = feignRestTemplate;
        packages = msInterceptorProperties.getOnlyPackages().split(",");
    }

    /**
     * 拦截器前置处理
     *
     * @param request 请求
     * @param response 响应
     * @param handler 处理器
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        REQUEST_INFORMATION.set(request);
        if (handler instanceof HandlerMethod) {
            // 包路径匹配拦截
            if (!packageMatch((HandlerMethod) handler)) {
                return true;
            }
            // 内外部接口拦截
            if (msInterceptorProperties.getInsideOrOutside()) {
                if (!GatewayValidator.valid(request, (HandlerMethod) handler)) {
                    return buildRefusedResponse(response, HttpResult.INNER_INTERFACE, HttpResult.INNER_INTERFACE.getMessage(),
                            msInterceptorProperties.getDebug(), request);
                }
            }
            // 鉴权拦截
            if (msInterceptorProperties.getAuthentication()) {
                if (request.getHeader(GatewayValidator.GATEWAY) != null
                        && !UrlValidator
                        .valid(request, feignRestTemplate, msInterceptorProperties, (HandlerMethod) handler)) {
                    return buildRefusedResponse(response, HttpResult.AUTH_FAIL, HttpResult.AUTH_FAIL.getMessage(),
                            msInterceptorProperties.getDebug(), request);
                }
            }
            // 用户信息拦截
            if (msInterceptorProperties.getUser()) {
                if (!UserValidator.valid(request, (HandlerMethod) handler, msInterceptorProperties)) {
                    return buildRefusedResponse(response, HttpResult.NO_USER_INFO, HttpResult.NO_USER_INFO.getMessage(),
                            msInterceptorProperties.getDebug(), request);
                }
            }
            return true;
        } else {
            // 请求类型拦截
            return !msInterceptorProperties.getOnlyHandlerMethod();
        }
    }

    /**
     * 包头匹配
     *
     * @param handler
     * @return
     */
    private Boolean packageMatch(HandlerMethod handler) {
        if (packages != null) {
            String packageName = handler.getBean().getClass().getPackage().getName();
            boolean match = Boolean.FALSE;
            for (String aPackage : packages) {
                if (packageName.startsWith(aPackage)) {
                    match = Boolean.TRUE;
                    break;
                }
            }
            return match;
        } else {
            return true;
        }
    }

    /**
     * 获取请求头信息
     *
     * @param request
     */
    private Map<String, String> getHeaders(HttpServletRequest request) {
        Map<String, String> headers = new HashMap<>(8);
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            headers.put(name, request.getHeader(name));
        }
        return headers;
    }

    /**
     * 构建拒绝响应
     */
    private Boolean buildRefusedResponse(HttpServletResponse response, HttpResult code, String message, Boolean debug,
            HttpServletRequest request) {
        Result refused;
        if (debug) {
            refused = Result.result(code.getCode(), message, getHeaders(request));
        } else {
            refused = Result.fail(message, code);
        }
        response.setCharacterEncoding("UTF-8");
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        try (PrintWriter out = response.getWriter()) {
            out.append(JSON.toJSONString(refused));
        } catch (IOException e) {
            log.warn("buildRefusedResponse IOException: {}", LogUtil.getExceptionMessage(e));
        }
        return false;
    }

    /**
     * 拦截器后置处理
     *
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        USER_INFORMATION.remove();
        REQUEST_INFORMATION.remove();
    }

    /**
     * 拦截器完成处理
     *
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {

    }
}
